package com.packenius.library.xspdf;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

class XSCreator4PageContent {
  /**
   * Creates the whole content of a page in a single String.
   */
  String getPageContentString(XSPage page, XSCreator creator, List<XSAnnotation> annotationObjects) {
    StringBuilder s = new StringBuilder();
    setPageBackgroundColor(page, s);
    List<Integer> layers = getLayerIDs(page.columns);
    for (Integer layerID : layers) {
      drawLayerColumns(page, s, layerID, annotationObjects);
    }
    return s.toString();
  }

  private void drawLayerColumns(XSPage page, StringBuilder s, Integer layerID, List<XSAnnotation> annotationObjects) {
    List<XSColumn> columns = getColumnsWithLayerID(page.columns, layerID);
    for (int i = columns.size() - 1; i >= 0; i--) {
      XSColumn column = columns.get(i);
      setColumnBackgroundColorIfVisible(s, page, column);
      addImagesToPage(page, column, s, annotationObjects);
    }
    for (XSColumn column : columns) {
      addTextLinesToPage(page, column, s, annotationObjects);
    }
  }

  private List<Integer> getLayerIDs(List<XSColumn> columns) {
    List<Integer> list = new ArrayList<Integer>();
    for (XSColumn column : columns) {
      if (!list.contains(column.layerID)) {
        list.add(column.layerID);
      }
    }
    Collections.sort(list);
    return list;
  }

  private List<XSColumn> getColumnsWithLayerID(List<XSColumn> columns, int layerID) {
    List<XSColumn> list = new ArrayList<XSColumn>();
    for (XSColumn column : columns) {
      if (column.layerID == layerID) {
        list.add(column);
      }
    }
    return list;
  }

  private void setPageBackgroundColor(XSPage page, StringBuilder s) {
    if (!Color.WHITE.equals(page.backgroundColor)) {
      s.append("q");
      appendColorComponents(s, page.backgroundColor);
      s.append("rg 0 0 " + page.pageSize.width + " " + page.pageSize.height);
      s.append(" re F Q");
    }
  }

  private void addImagesToPage(XSPage page, XSColumn column, StringBuilder s, List<XSAnnotation> annotationObjects) {
    double columnX = column.x;
    double columnY = column.y;
    for (XSImage image : column.images) {
      double x = image.x + columnX;
      double y = image.y + columnY;
      double width = image.width;
      double height = image.height;
      XSLink link = image.link;

      // Add rectangle around the image.
      double margin = image.margin;
      s.append("\rq 0.7 0.7 0.8 rg 0 w " + (x - margin / 2) + " " + (y - margin / 2) + " " + (width + margin) + " "
        + (height + margin) + " re F Q");

      // Draw image.
      s.append("\rq " + width + " 0 0 " + height + " " + x + " " + y + " cm /Im" + image.imageID + " Do Q");

      if (link != null) {
        annotationObjects.add(new XSLinkAnnotation(link, x, y, width, height));
      }
    }
  }

  private void addTextLinesToPage(XSPage page, XSColumn column, StringBuilder s, List<XSAnnotation> annotationObjects) {
    double columnX = column.x;
    double columnY = column.y;
    for (XSTextLine textLine : column.textLines) {
      if (!textLine.textParts.isEmpty()) {
        createContentForTextLine(page, s, textLine, columnX, columnY, annotationObjects);
      }
    }
  }

  private void setColumnBackgroundColorIfVisible(StringBuilder s, XSPage page, XSColumn column) {
    Color backgroundColor = column.backgroundColor;
    if (backgroundColor == null) {
      return;
    }
    double margin = column.margin / 2;
    double x = column.x - margin;
    double y = column.y - margin;
    double width = column.width + 2 * margin;
    double height = column.height + 2 * margin;
    s.append("\rq");
    appendColorComponents(s, backgroundColor);
    s.append("rg " + x + " " + y + " " + width + " " + height);
    s.append(" re F Q");
  }

  private void createContentForTextLine(XSPage page, StringBuilder s, XSTextLine textLine, double columnX,
      double columnY, List<XSAnnotation> annotationObjects) {
    s.append("\r");
    double y = textLine.getBaselineYInColumn() + columnY;
    List<XSTextPart> textParts = textLine.textParts;
    int textPartCount = textParts.size();

    int firstTextPartID = 0;

    // Set x coordinate, word spacing and character spacing.
    double x = getStartXCoordinateFrTextLine(textLine, columnX);
    double wordSpacing = 0.0, characterSpacing = 0.0;
    XSTextPart textPart0 = textParts.get(0);
    double remainingSpace = textLine.getRemainingSpace();
    XSFontUsage fontUsage0 = textPart0.fontUsage;
    if (fontUsage0.alignment == XSAlignment.Justification) {
      if (!textLine.leftAlignWhenJustification) {
        if (textParts.size() > 1) {
          wordSpacing = getWordSpacingForMoreThanOneTextPartLine(textParts, textPartCount, remainingSpace, fontUsage0);
        } else { // if (textParts.size() == 1)
          double characterCount = textPart0.text.length();
          characterSpacing = characterCount > 1 ? remainingSpace / (characterCount - 1) : 0.0;
        }
      }
    }

    // Loop over all TextParts of this text line.
    while (firstTextPartID < textPartCount) {
      // Get following words with same font usage.
      XSTextPart firstTextPart = textParts.get(firstTextPartID);
      XSFontUsage firstFontUsage = firstTextPart.fontUsage;
      StringBuilder sbText = new StringBuilder();
      sbText.append(firstTextPart.text);

      if (firstTextPartID > 0) {
        s.append("\n");
      }

      if (fontUsage0.formattingMode == XSTextFormattingMode.AutoFormat) {
        if (firstTextPartID > 0) {
          // There is already something printed in this line!
          x += firstFontUsage.spaceWidth + wordSpacing;
        }
      }

      double width = firstTextPart.width;
      if (firstTextPart.text.equals(" ")) {
        width += wordSpacing;
      } else {
        width += (sbText.length() - 1) * characterSpacing;
      }
      firstTextPart.realX1 = x;
      firstTextPart.realX2 = x + width;

      // Get all text parts of this line that have the same font usage.
      int lastTextPartID = firstTextPartID + 1;
      while (lastTextPartID < textPartCount) {
        XSTextPart lastTextPart = textParts.get(lastTextPartID);
        if (!lastTextPart.fontUsage.equals(firstFontUsage)) {
          break;
        }
        String lastText = lastTextPart.text;
        switch (fontUsage0.formattingMode) {
        case AutoFormat:
          width += firstFontUsage.spaceWidth + wordSpacing;
          width += (lastText.length() - 1) * characterSpacing;
          sbText.append(" " + lastText);
          break;
        case NoFormatting:
          // lastTextPart.realX1 = x + width;
          if (lastText.equals(" ")) {
            width += wordSpacing;
          } else {
            width += (lastText.length() - 1) * characterSpacing;
          }
          sbText.append(lastText);
          break;
        }
        lastTextPart.realX1 = x + width;
        width += lastTextPart.width;
        lastTextPart.realX2 = x + width;
        lastTextPartID++;
      }

      String text = sbText.toString();
      List<XSFontEncodingUsage> fontEncodingUsage = getFontEncodingUsageList(text, firstFontUsage);

      // Create text block within the PDF stream with a single font usage.
      addPdfTextBlockWithCoordinatesAndFontUsage(page, s, x, y, text, firstFontUsage, fontEncodingUsage,
        wordSpacing,
        characterSpacing);

      // Underline with red line.
      // s.append("\n 1.0 0.0 0.0 RG"); // Red as stroke color.
      // s.append(" 0 w"); // Line as thin as possible (1 device pixel line
      // width).
      // s.append(" " + x + " " + y + " m"); // Move to coordinates, beginning a
      // new subpath.
      // s.append(" " + (x + width) + " " + y + " l"); // Append straight line
      // segment.
      // s.append(" S"); // Stroke path.

      // Move to next word (block).
      firstTextPartID = lastTextPartID;
      x += width;
    }

    XSLink lastDestinationName = null;
    double destX1 = 0, destX2 = 0;
    for (XSTextPart textPart : textParts) {
      if (lastDestinationName != null) {
        if (lastDestinationName.equals(textPart.link)) {
          destX2 = textPart.realX2;
        }
        else {
          annotationObjects.add(new XSLinkAnnotation(lastDestinationName, destX1, textLine.yStartInColumn + textLine.column.y
            - textLine.getHeight(), destX2 - destX1, textLine.getHeight()));
          lastDestinationName = textPart.link;
          destX1 = textPart.realX1;
          destX2 = textPart.realX2;
        }
      }
      else {
        if (textPart.link != null) {
          lastDestinationName = textPart.link;
          destX1 = textPart.realX1;
          destX2 = textPart.realX2;
        }
      }
    }
    if (lastDestinationName != null) {
      annotationObjects.add(new XSLinkAnnotation(lastDestinationName, destX1, textLine.yStartInColumn + textLine.column.y
        - textLine.getHeight(),
        destX2 - destX1,
        textLine.getHeight()));
    }
  }

  private double getWordSpacingForMoreThanOneTextPartLine(List<XSTextPart> textParts, int textPartCount,
      double remainingSpace, XSFontUsage fontUsage0) {
    double wordSpacing;
    int wordCount = 0;
    switch (fontUsage0.formattingMode) {
    case AutoFormat:
      wordCount = textPartCount - 1;
      break;
    case NoFormatting:
      for (XSTextPart tp : textParts) {
        if (tp.text.trim().length() > 0) {
          wordCount++;
        }
      }
      break;
    }
    wordSpacing = remainingSpace / wordCount;
    return wordSpacing;
  }

  private double getStartXCoordinateFrTextLine(XSTextLine textLine, double columnX) {
    List<XSTextPart> textParts = textLine.textParts;
    double startX = textLine.xStartInColumn + columnX;
    double remainingSpace = textLine.getRemainingSpace();
    switch (textParts.get(0).fontUsage.alignment) {
    case LeftAligned:
      break;
    case RightAligned:
      startX += remainingSpace;
      break;
    case Centered:
      startX += remainingSpace / 2.0;
      break;
    case Justification:
      break;
    }
    return startX;
  }

  /**
   * Get an array of font encoding usage data for printing the given text.
   */
  private List<XSFontEncodingUsage> getFontEncodingUsageList(String text, XSFontUsage fontUsage) {
    // Some objects from font usage.
    XST1StdFont fontClass = fontUsage.fontType.fontClass;
    Map<Character, Character> standardEncoding = fontClass.standardEncodingCodeFromUnicodeCharacter;

    List<XSFontEncodingUsage> list = new ArrayList<XSFontEncodingUsage>();
    int textLength = text.length();
    if (fontUsage.fontType.fontName.equals("ZapfDingbats")) {
      list.add(new XSFontEncodingUsage(null, 0, textLength));
      return list;
    } else if (fontUsage.fontType.fontName.equals("Symbol")) {
      Character appleCode = XSUnicodeMapping.getCodeFromName("apple");
      if (text.contains("" + appleCode)) {
        list.add(new XSFontEncodingUsage(fontClass.getAlternativeFontEncoding(appleCode), 0, textLength));
        return list;
      }
      list.add(new XSFontEncodingUsage(null, 0, textLength));
      return list;
    }

    // One of Helvetica, Courier or Times.
    int firstIndex = 0;
    XSAlternativeFontEncoding currentFontEncUsage = null;
    for (int i = 0; i < textLength; i++) {
      char ch = text.charAt(i);
      if (standardEncoding.get(ch) == null) {
        XSAlternativeFontEncoding altFontEncoding = fontClass.getAlternativeFontEncoding(ch);
        if (currentFontEncUsage == null) {
          currentFontEncUsage = altFontEncoding;
        } else if (currentFontEncUsage != altFontEncoding) {
          list.add(new XSFontEncodingUsage(currentFontEncUsage, firstIndex, i));
          currentFontEncUsage = altFontEncoding;
          firstIndex = i;
        }
      }
    }
    list.add(new XSFontEncodingUsage(currentFontEncUsage, firstIndex, textLength));
    return list;
  }

  private void addPdfTextBlockWithCoordinatesAndFontUsage(XSPage xsPage, StringBuilder s, double x, double y,
      String text, XSFontUsage fontUsage, List<XSFontEncodingUsage> fontEncodingUsageList, double wordSpacing,
      double characterSpacing) {
    // Begin text.
    s.append("BT");

    // Set coordinates for text.
    s.append(" ");
    s.append(x);
    s.append(" ");
    s.append(y);
    s.append(" Td");

    // Word spacing.
    // if (wordSpacing > 0.0) {
    s.append(" ");
    s.append(wordSpacing);
    s.append(" Tw");
    // }

    switch (fontUsage.renderMode) {
    case Fill:
      s.append(" 0 Tr");
      break;
    case FillAndStroke:
      s.append(" 2 Tr");
      break;
    case Stroke:
      s.append(" 1 Tr");
      break;
    case Invisible:
      s.append(" 3 Tr");
      break;
    }

    // Character spacing.
    // if (characterSpacing > 0.0) {
    s.append(" ");
    s.append(characterSpacing);
    s.append(" Tc");
    // }

    XSFontType fontType = fontUsage.fontType;

    Color lastFillColor = Color.BLACK;

    for (XSFontEncodingUsage fontEncodingUsage : fontEncodingUsageList) {
      // Set font and font size.
      s.append(" /");
      s.append(fontType.logicalName);
      XSAlternativeFontEncoding fontEncoding = fontEncodingUsage.fontEncoding;
      xsPage.usedFontTypesAndEncodings.add(new XSFontTypeAndEncodingInformation(fontType, fontEncoding));
      if (fontEncoding != null) {
        s.append(fontEncoding.getID());
      }
      s.append(" ");
      s.append(fontUsage.fontSize);
      s.append(" Tf");

      // Text fill color if not black.
      if (fontUsage.renderMode != XSTextRenderingMode.Stroke) {
        Color fillColor = fontUsage.textFillColor;
        if (!fillColor.equals(lastFillColor)) {
          appendColorComponents(s, fillColor);
          s.append("rg");
          lastFillColor = fillColor;
        }
      }

      // Text stroke color.
      if (fontUsage.renderMode != XSTextRenderingMode.Fill) {
        appendColorComponents(s, fontUsage.textStrokeColor);
        s.append("RG");
      }

      // Draw string.
      s.append(" (");
      int firstCharacterIndex = fontEncodingUsage.firstCharacterIndex;
      int lastCharacterIndex = fontEncodingUsage.lastCharacterIndex;
      String subtext = text.substring(firstCharacterIndex, lastCharacterIndex);
      s.append(encodeToStandardEncoding(fontType, subtext, fontEncoding));
      s.append(")");
      s.append(" Tj");
    }

    // End text.
    s.append(" ET");
  }

  private void appendColorComponents(StringBuilder s, Color color) {
    float[] colComps = color.getColorComponents(null);
    s.append(" ");
    s.append(colComps[0]);
    s.append(" ");
    s.append(colComps[1]);
    s.append(" ");
    s.append(colComps[2]);
    s.append(" ");
  }

  private String encodeToStandardEncoding(XSFontType fontType, String text, XSAlternativeFontEncoding fontEncoding) {
    int count = text.length();
    char[] ca = new char[count];
    int i = 0;
    Map<Character, Character> standardEncoding = fontType.fontClass.standardEncodingCodeFromUnicodeCharacter;
    final Map<Character, Character> characterCodeFromUnicodeCharacter;
    if (fontEncoding == null) {
      characterCodeFromUnicodeCharacter = standardEncoding;
    } else {
      characterCodeFromUnicodeCharacter = fontEncoding.getCharacterEncodingMap();
    }
    for (char ch : text.toCharArray()) {
      Character code = standardEncoding.get(ch);
      if (code == null) {
        ca[i++] = characterCodeFromUnicodeCharacter.get(ch);
      } else {
        ca[i++] = code;
      }
    }
    String s = new String(ca);

    // Replace all characters that must be escaped inside the string: ( \ )
    return XSStatics.escapeStandardStringCharacters(s);
  }
}
